package com.vteba.security.interceptor;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.access.SecurityMetadataSource;
import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
import org.springframework.security.access.intercept.InterceptorStatusToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;

import com.vteba.common.constant.CommonConst;
import com.vteba.security.filter.DefaultUserAuthenticationToken;
import com.vteba.security.user.IUserDetails;

/**
 * spring security 自定义过滤器实现，spring security 会回调<br>
 * FilterInvocationSecurityMetadataSource的实现。<br>
 * 1、<br>
 * 2、<br>
 * 3、<br>
 * @author yinlei
 * date 2012-4-6 下午6:16:28
 */
public class FilterSecurityInterceptorImpl extends AbstractSecurityInterceptor implements Filter {
	private static final String ANON_USER = "anonymousUser";
	private FilterInvocationSecurityMetadataSource securityMetadataSource;
	
	private RedisTemplate<String, IUserDetails> redisTemplate;
	// 白名单列表
	private Set<String> whiteListUrl = new HashSet<>();
	// 是否是集群模式
	private boolean cluster;
	// 访问拒绝后重定向的页面，一般是登录页
	private String redirectUrl;
	// 是否允许未配置的未知请求
	private boolean allowUnknownRequest;
	
	public void setSecurityMetadataSource(
			FilterInvocationSecurityMetadataSource securityMetadataSource) {
		this.securityMetadataSource = securityMetadataSource;
		
		// filterUrlSet.add("/common/random");
	}

	// 进入这里面的请求都是需要保护的，js、css都已经在外面被过滤了
	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		FilterInvocation invocation = new FilterInvocation(request, response, chain);
		
		HttpServletRequest servletRequest = (HttpServletRequest) request;
		String url = servletRequest.getServletPath();
		// 这个对管理系统是没有意义的，就是要控制的全面
//		boolean allow = false;
//		if (allowUnknownRequest) { // 如果允许未知的请求，配置的白名单，将失效
//			allow = true;
//		} else {
//			allow = filterUrlSet.contains(url); // 要放行的白名单的url
//		}
		boolean allow = whiteListUrl.contains(url); // 要放行的白名单的url
		if (!allow) { // 不是白名单，检测用户登录状态
			boolean notLogin = isNotLogin();
			if (cluster) { // 集群状态，如果没有用户信息，尝试从本地cookie和缓存中恢复
				if (notLogin) { // 没有登录
					Map<String, String> result = getAccountInfo(servletRequest); // 从cookie中获取账户信息
					// 两个键（账户和租户代码），都不能为空，否则无法恢复用户 // 这里要调整，兼容不使用租户代码的
					int size = result.size();
					if (size == 2 || size == 1) {
						// redis中不为空，说明服务器宕机，构造一个用户信息放入session
						IUserDetails user = getUser(result); // 从缓存中获取用户信息
						if (user != null) {
							DefaultUserAuthenticationToken token = new DefaultUserAuthenticationToken(user, user.getPassword(), user.getAuthorities());
							token.setAuthenticated(true);
							token.setOrgCode(user.getTenantCode());
							token.setDetails(user);
							SecurityContextHolder.getContext().setAuthentication(token);
							
							servletRequest.getSession().setAttribute(CommonConst.CONTEXT_USER, user);
							notLogin = false; // 构造完用户，放入session中，登录了
						}
					}
				}
			}
			if (notLogin) { // 没有登录，重定向
				HttpServletResponse servletResponse = (HttpServletResponse) response;
				String context = servletRequest.getServletContext().getContextPath();
				servletResponse.sendRedirect(context + redirectUrl);
				return;
			}
		}
		// 检查登录通过，继续执行请求
		invoke(invocation);
	}

	private boolean isNotLogin() {
		Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
		// 没有用户 或者是匿名用户
		if (authentication == null || authentication.getPrincipal().equals(ANON_USER)) {
			return true;
		}
		return false;
	}
	
	/**
	 * 从cookie中获取账户和租户代码信息
	 * @param request
	 * @return
	 */
	private Map<String, String> getAccountInfo(HttpServletRequest request) {
		Map<String, String> result = new HashMap<>();
		Cookie[] cookies = request.getCookies();
		// 从本地拿用户账户信息，用于集群登录
		if (cookies != null) {
			for (Cookie cookie : cookies) {
				String name = cookie.getName();
				if (name.equals(CommonConst.SEC_ACCOUNT)) { // 账号
					try {
						String account = URLDecoder.decode(cookie.getValue(), "UTF-8"); // 可能是中文
						if (StringUtils.isNotBlank(account)) {
							result.put(CommonConst.SEC_ACCOUNT, account);
						} 
					} catch (UnsupportedEncodingException e) {
						// ignore
					}
				} else if (name.equals(CommonConst.SEC_TENANT)) { // 租户代码
					String tenantId = cookie.getValue();
					if (StringUtils.isNotBlank(tenantId)) {
						result.put(CommonConst.SEC_TENANT, tenantId);
					}
				}
			}
		}
		return result;
	}
	
	/**
	 * 从缓存中获取用户
	 * @param result 账户和租户代码
	 * @return
	 */
	private IUserDetails getUser(Map<String, String> result) {
		String key = CommonConst.SPR_SEC_PREFIX;
		int size = result.size();
		if (size == 2) {
			key = key + result.get(CommonConst.SEC_ACCOUNT) + result.get(CommonConst.SEC_TENANT);
		} else if (size == 1) {
			key = key + result.get(CommonConst.SEC_ACCOUNT);
		}
		IUserDetails user = redisTemplate.opsForValue().get(key);
		return user;
	}
	
	private void invoke(FilterInvocation invocation) throws IOException,
			ServletException {
		InterceptorStatusToken token = super.beforeInvocation(invocation);
		try {
			invocation.getChain().doFilter(invocation.getRequest(), invocation.getResponse());
		} finally {
			super.afterInvocation(token, null);
		}
	}

	@Override
	protected InterceptorStatusToken beforeInvocation(Object object) {
		return super.beforeInvocation(object);
	}

	public void destroy() {
	}

	public void init(FilterConfig filterConfig) throws ServletException {
		
	}

	public Class<? extends Object> getSecureObjectClass() {
		return FilterInvocation.class;
	}

	public SecurityMetadataSource obtainSecurityMetadataSource() {
		return this.securityMetadataSource;
	}

	/**
	 * use {@link #setWhiteListUrl(Set)}
	 * @param filterUrlSet
	 */
	@Deprecated
	public void setFilterUrlSet(Set<String> filterUrlSet) {
		this.whiteListUrl = filterUrlSet;
	}

	public RedisTemplate<String, IUserDetails> getRedisTemplate() {
		return redisTemplate;
	}

	public void setRedisTemplate(RedisTemplate<String, IUserDetails> redisTemplate) {
		this.redisTemplate = redisTemplate;
	}

	public boolean isCluster() {
		return cluster;
	}

	public void setCluster(boolean cluster) {
		this.cluster = cluster;
	}

	public String getRedirectUrl() {
		return redirectUrl;
	}

	public void setRedirectUrl(String redirectUrl) {
		this.redirectUrl = redirectUrl;
	}

	public boolean isAllowUnknownRequest() {
		return allowUnknownRequest;
	}

	public void setAllowUnknownRequest(boolean allowUnknownRequest) {
		this.allowUnknownRequest = allowUnknownRequest;
	}

	public Set<String> getWhiteListUrl() {
		return whiteListUrl;
	}

	public void setWhiteListUrl(Set<String> whiteListUrl) {
		this.whiteListUrl = whiteListUrl;
	}

}
